xlv.obj.rowset={
	extend:function(){
		this.extend('xlv.obj.webObject');
	},
	implement:function(){
	},
	create:function(tag){
		var div=document.createElement((tag||'div'));
		div.setAttribute('xlvobject','xlv.obj.rowset');
		return div;			
	},
	properties:{
		name:null,
		propXPath:'properties',
		dataXPath:null,
		rowName:'r',
		notifySetValue:false,
		autoPost:'none'//post, none,default is none
	},
	method:{
		initial:function(){
			this.$super('xlv.obj.webObject.initial');
			this.$V.currentNode=null;
			this.$V.traceNode=null;
			this.$V.autoPostMoveToNode=null;
			this.$V.updated=false;
			if(this.$P.propXPath)this.$P.propXPath=this.$P.propXPath.replace(/\./g,'/');
			if(this.$P.dataXPath)this.$P.dataXPath=this.$P.dataXPath.replace(/\./g,'/');
		},
		start:function(){
			if(!this.$V.__started){
				if(this.$P.objectURL) {
					this.$V.lastMethodName='get-properties';
					this.queueTimeout((function(){this.request(null, 'get-properties', null);}).bind(this));
				}
				this.$V.__started=true;
			}else if(this.$V.__mvcArray){
					var mvc_array=this.$V.__mvcArray;
					this.$V.__mvcArray=null;
					this.$V.__queueNotifyMVC=true;
					var memo=this.createMemoObject({type:'create',methods:'start',from:this.$P.name,to:this.$P.objectURL,cancelable:false});
					this.notifyMVC('beforeModelChanged','setChanged',memo,mvc_array);
					this.notifyMVC('modelChanged','setChanged',memo,mvc_array);
					this.notifyMVC('afterModelChanged','setChanged',memo,mvc_array);
					memo=this.createMemoObject({type:'cursorMove',methods:'start',from:this.$V.currentNode,to:this.$V.currentNode});
					this.notifyMVC('beforeModelChanged','cursorMove',memo,mvc_array);
					this.notifyMVC('modelChanged','cursorMove',memo,mvc_array);
					this.notifyMVC('afterModelChanged','cursorMove',memo,mvc_array);
					this.$V.__queueNotifyMVC=false;
			}
		},
		dispose:function(){
			this.$super('xlv.obj.webObject.dispose');
		},
		/*****/
		webObjectResponse:function(){
			var res_method_names=this.xml_responseMethodNames();
			if(res_method_names=='get-properties'){
				if(this.$P.objectURL && this.$P.methodName){ 
					this.queueTimeout((function(){if(!this.webMethod(this.$P.methodName))this._notifiyReady();}).bind(this));
				}else{
					var memo=this.createMemoObject({type:'create',methods:res_method_names,cancelable:false});
					this.notifyMVC('beforeModelChanged','setChanged',memo);
					this.notifyMVC('modelChanged','setChanged',memo);
					this.notifyMVC('afterModelChanged','setChanged',memo);
					this._notifiyReady();
				}
				return;
			}
			var clone_response=(arguments.length>0)?arguments[0]:false;
			var p_xml=this.xml_properties;
			if(p_xml.responseXML && (p_xml.responseXML != p_xml.currentXML)){
				var memo=this.createMemoObject({type:'create',methods:res_method_names,cancelable:false});
				var datapath=(this.$P.dataXPath)?this.$P.dataXPath.replace(/properties\//,''):null;
				var hit_data=(datapath)?this.xml_selectSingleNode(datapath,p_xml.responseXML.documentElement):null;
				memo.from=this.$V.lastMethodName;
				this.notifyMVC('beforeModelChanged','setChanged',memo);
				var prop=this.xml_selectSingleNode(this.$P.propXPath);
				if(this.$P.returnData=='replace'){
					var methods=this.xml_selectNodes('child::*',p_xml.responseXML.documentElement);
					for(var i=0;i<methods.length;++i){
						var return_node=this.xml_selectSingleNode(this.$P.propXPath+'/'+methods[i].nodeName+'/return');
						if(return_node)	return_node.parentNode.removeChild(return_node);
					}
					xlv.util.xmlExtendNodes(prop,p_xml.responseXML.documentElement,'extend',clone_response);
				}else xlv.util.xmlExtendNodes(prop,p_xml.responseXML.documentElement,this.$P.returnData,clone_response);
				if(hit_data){
					this.$V.traceNode=this.$V.currentNode;
					this.$V.currentNode=this.xml_selectSingleNode(this.$P.dataXPath+'/'+this.$P.rowName+'[1]');
				}
				this.notifyMVC('modelChanged','setChanged',memo);
				this.notifyMVC('afterModelChanged','setChanged',memo);
				this._moveToNode(this.$V.currentNode);
				this._notifiyReady();
			}
			this.$V.updated=false;
		},
		getNode:function(xpath,nth){
			if(xpath.startsWith('./')){
				return this.xml_selectSingleNode(xpath,this.$V.currentNode);
			}
			var	root_node=(arguments.length>2&&Object.isElement(arguments[2]))?arguments[2]:
					(arguments.length>1 && Object.isElement(arguments[1])) ? arguments[1]:null;
			nth=(root_node)? null:nth;
			if(root_node)return this.xml_selectSingleNode(xpath.replace(/\./g,'/'),root_node);
			if(!nth&&(xpath.startsWith(this.$P.propXPath))){
				return this.xml_selectSingleNode(xpath.replace(/\./g,'/'),root_node);
			}else{
				nth=(nth==null||nth<=0)?this.rowNumber():nth;
				xpath=this.$P.dataXPath+'/'+this.$P.rowName+'['+nth+']'+((xpath=='')?'':'/')+xpath;
				return this.xml_selectSingleNode(xpath,null);
			}
		},
		getValue:function(xpath,nth){
			var root_node=(arguments.length>2&&Object.isElement(arguments[2]))?arguments[2]:
				(arguments.length>1 && Object.isElement(arguments[1])) ? arguments[1]:null;
			nth=(root_node)? null:nth;
			//
			var node=this.getNode(xpath,nth,root_node);
			return node ? this.xml_getNodeValue(node): '';
		},
		setValue:function(value,xpath,nth){
			var root_node=(arguments.length>3&&Object.isElement(arguments[3]))?arguments[3]:
				(arguments.length>2 && Object.isElement(arguments[2]))?arguments[2]:null;
			nth=(root_node)? null:nth;
			var node=this.getNode(xpath,nth,root_node);
			if(node){
				if(!nth&&(xpath.startsWith(this.$P.propXPath))&&!this.$P.notifySetValue){
					this.xml_setNodeValue(node,value);
					return;
				}
				var old=this.xml_getNodeValue(node);
				if(old==value)return;
				if(node.nodeType==1){
					if(node.getAttributeNode('xlv_old')==null)node.setAttribute('xlv_old',old);
					if(node.parentNode.attributes.length==0) node.parentNode.setAttribute('xlv_upd','1');
				}
				this.xml_setNodeValue(node,value);
				this.$V.updated=true;
				var memo=this.createMemoObject({type:'update',methods:'setValue',src:node,from:old,to:value,'value':value,cancelable:false});
				this.notifyMVC('modelChanged','nodeChanged',memo);
			}
		},
		/*****/
		select:function(node){
			var data=this.xml_selectSingleNode(this.$P.dataXPath);
			var prop=this.xml_selectSingleNode(this.$P.propXPath);
			while(node){
				if(node.tagName==this.$P.rowName && node.parentNode==data){
					this._moveToNode(node);
					return;
				}
				node=node.parenNode;
				if(node==prop||node==null)break;
			}
		},
		/****/
		_moveToNode:function(node){
			this.queueTimeout(function(){this.__moveToNode(node);}.bind(this));
			return true;
		},
		__moveToNode:function(node){
			if(!this.movable(this.$V.currentNode,node))return false;
			if(this.$P.autoPost=='post' && this.isDirtyRow()){
				this.$V.autoPostMoveToNode=this.$V.currentNode=node;
				if(this.post())return true;
			}else if(this.$P.autoPost=='cancel' && this.isDirtyRow()){
				this.cancel(this.$V.currentNode,false);
			}
			var memo=this.createMemoObject({type:'cursorMove',methods:'__moveToNode',from:this.$V.currentNode,to:node});
			this.notifyMVC('beforeModelChanged','cursorMove',memo);
			if(memo.cancel)return false;
			if(node != this.$V.currentNode){
				this.$V.traceNode=this.$V.currentNode;
				this.$V.currentNode=node;
			}
			memo.cancelable=false;
			this.notifyMVC('modelChanged','cursorMove',memo);
			this.notifyMVC('afterModelChanged','cursorMove',memo);
			return true;
		},
		_moveToPath:function(xpath){
			var n=this.xml_selectSingleNode(xpath,this.$V.currentNode);
			return (n && n.parentNode)? this._moveToNode(n) : false;
		},
		/**navigation**/
		movable:function(from_node,to_node){
			return true;
		},
		first:function(){
			return this._moveToPath('preceding-sibling::'+this.$P.rowName+'[last()]');
		},
		firstable:function(){
			return !this.isBOF();
		},
		prior:function(){
			return this._moveToPath('preceding-sibling::'+this.$P.rowName+'[1]');
		},
		priorable:function(){
			return !this.isBOF();
		},
		next:function(){
			return this._moveToPath('following-sibling::'+this.$P.rowName+'[1]');
		},
		nextable:function(){
			return !this.isEOF();
		},
		last:function(){
			return this._moveToPath('following-sibling::'+this.$P.rowName+'[last()]');
		},
		lastable:function(){
			return !this.isEOF();
		},
		position:function(pos){
			return this._moveToPath('../'+this.$P.rowName+'['+pos+']');
		},
		updatable:function(row){
			row=(row||this.$V.currentNode);
			return !this.isDeletedRow(row);
		},
		isUpdatedRow:function(row){
			row=(row||this.$V.currentNode);
			return (row!=null&&row.getAttributeNode('xlv_upd')!=null&&row.parentNode!=null);
		},
		_updatePost:function(row){
			row=(row||this.$V.currentNode);
			if(this.isUpdatedRow(row)){
				var olds=this.xml_selectNodes('child::*[@xlv_old]',row);
				for(var d=0;d<olds.length;++d) olds[d].removeAttribute('xlv_old');
				row.removeAttribute('xlv_upd');
			}
		},
		_updateCancel:function(row){
			row=(row||this.$V.currentNode);
			if(row){
				var olds=this.xml_selectNodes('child::*[@xlv_old]',row);
				for(var i=0;i<olds.length;++i){
					this.xml_setNodeValue(olds[i],olds[i].getAttribute('xlv_old'));
					olds[i].removeAttribute('xlv_old');
				}
				row.removeAttribute('xlv_upd');
			}
		},
		postable:function(){
			return true;//return this.isDirtyRow();//this.$V.currentState=='normal';
		},
		post:function(){
			if(!this.postable()) return false;
			var methods='',ns,memo;
			ns=this.xml_selectNodes(this.$P.dataXPath+'/'+this.$P.rowName+'[@xlv_upd]');
			for(var i=0;i<ns.length;++i) {
				memo=this.createMemoObject({type:'updated',methods:'post',cancelable:false});
				memo.src=memo.from=memo.to=ns[i];
				this.notifyMVC('beforeModelChanged','rowChanged',memo);
				this._updatePost(ns[i]);
				this.notifyMVC('modelChanged','rowChanged',memo);
				this.notifyMVC('afterModelChanged','rowChanged',memo);
			}
			var current_row=this.$V.currentNode;
			ns=this.xml_selectNodes(this.$P.dataXPath+'/'+this.$P.rowName+'[@xlv_del]');
			for(var i=0;i<ns.length;++i) {
				if(current_row==ns[i]) current_row=this.advanceRow(current_row);
				memo=this.createMemoObject({type:'removed',methods:'post',cancelable:false});
				memo.src=memo.from=memo.to=ns[i];
				this.notifyMVC('beforeModelChanged','rowChanged',memo);
				this._removePost(ns[i]);
				this.notifyMVC('modelChanged','rowChanged',memo);
				this.notifyMVC('afterModelChanged','rowChanged',memo);
			}
			ns=this.xml_selectNodes(this.$P.dataXPath+'/'+this.$P.rowName+'[@xlv_ins]');
			for(var i=0;i<ns.length;++i) {
				memo=this.createMemoObject({type:'inserted',methods:'post',cancelable:false});
				memo.src=memo.from=memo.to=ns[n];
				this.notifyMVC('beforeModelChanged','rowChanged',memo);
				this._insertPost(ns[i]);
				this.notifyMVC('modelChanged','rowChanged',memo);
				this.notifyMVC('afterModelChanged','rowChanged',memo);
			}
			this.$V.updated=false;
			if(this.$V.autoPostMoveToNode){
				var next=this.$V.autoPostMoveToNode;
				this.$V.autoPostMoveToNode=null;
				this._moveToNode(next);
			}else if(current_row!=this.$V.currentNode)this._moveToNode(current_row);
			
			return true;
		},
		cancel:function(row,dont_move){
			row=(row||this.$V.currentNode);
			var memo=this.createMemoObject({type:'cancel',methods:'cancel',src:row,from:row,to:row});
			if(this.isInsertedRow(row)) {
				memo.value='insert';
				var move_to_row=this.advanceRow(this.$V.currentNode);
				memo.to=move_to_row;
				this.notifyMVC('beforeModelChanged','rowChanged',memo);
				if(memo.cancel)return false;
				this._insertCancel(row);
				memo.cancelable=false;
				this.notifyMVC('modelChanged','rowChanged',memo);
				this.notifyMVC('afterModelChanged','rowChanged',memo);
				if(dont_move !== false)this._moveToNode(move_to_row);
			}else if(this.isUpdatedRow(row)){
				memo.value='update';
				this.notifyMVC('beforeModelChanged','rowChanged',memo);
				if(memo.cancel)return false;
				this._updateCancel(row);
				memo.cancelable=false;
				this.notifyMVC('modelChanged','rowChanged',memo);
				this.notifyMVC('afterModelChanged','rowChanged',memo);
			}else if(this.isRemovedRow(row)){
				memo.value='remove';
				this.notifyMVC('beforeModelChanged','rowChanged',memo);
				if(memo.cancel)return false;
				this._removeCancel(row);
				memo.cancelable=false;
				this.notifyMVC('modelChanged','rowChanged',memo);
				this.notifyMVC('afterModelChanged','rowChanged',memo);
			}
			return true;
		},
		cancelable:function(row){
			row=(row||this.$V.currentNode);
			return (row!=null &&this.isDirtyRow(row));
		},
		removePost:function(row){
			return this.remove(row)? this.post() : true;
		},
		remove:function(row){
			row=(row||this.$V.currentNode);
			if(!this.removeable(row)) return false;
			var memo=this.createMemoObject({type:'remove',methods:'remove',src:row,from:row});
			this.notifyMVC('beforeModelChanged','rowChanged',memo);
			if(memo.cancel)return false;
			row.setAttribute('xlv_del','1');
			memo.cancelable=false;
			this.notifyMVC('modelChanged','rowChanged',memo);
			this.notifyMVC('afterModelChanged','rowChanged',memo);
			return true;
		},
		removeable:function(row){
			row=(row||this.$V.currentNode);
			return (row!=null && !this.isInsertedRow(row));
		},
		isRemovedRow:function(row){
			row=(row||this.$V.currentNode);
			return (row!=null&&(row.getAttributeNode('xlv_del')!=null)&&row.parentNode!=null);
		},
		_removePost:function(row){
			row=(row||this.$V.currentNode);
			if(this.isRemovedRow(row))row.parentNode.removeChild(row);
		},
		_removeCancel:function(row){
			row=(row||this.$V.currentNode);
			if(this.isRemovedRow(row))row.removeAttribute('xlv_del');
		},
		insert:function(){
			if(!this.insertable()) return null;
			var memo=this.createMemoObject({type:'insert',methods:'insert',cancelable:true});
			this.notifyMVC('beforeModelChanged','rowChanged',memo);
			if(memo.cancel)return null;
			this.$V.traceNode=this.$V.currentNode;
			var ins=this.insertBlankAfterRow(this.$V.currentNode);
			ins.setAttribute('xlv_ins','1');
			this.setDefaultValue(ins);
			memo.src=ins;
			memo.cancelable=false;
			memo.from=this.$V.traceNode;
			memo.to=ins;
			memo.value=ins;
			this.notifyMVC('modelChanged','rowChanged',memo);
			this.notifyMVC('afterModelChanged','rowChanged',memo);
			this._moveToNode(ins);
			return ins;
		},
		isInsertedRow:function(row){
			row=(row||this.$V.currentNode);
			return (row!=null&&row.getAttributeNode('xlv_ins')!=null&&row.parentNode!=null);
		},
		insertable:function(){
			return true;
		},
		makeBlankRow:function(){
			return (this.$V.currentNode)?this.$V.currentNode.cloneNode(true):this.xml_createElement(this.$P.rowName);
		},
		insertBlankBeforeRow:function(row){
			row=(row||this.$V.currentNode);
			var parent=this.xml_selectSingleNode(this.$P.dataXPath);
			var blank=this.makeBlankRow();
			parent.insertBefore(blank,row);
			return blank;
		},
		insertBlankAfterRow:function(row){
			row=(row||this.$V.currentNode);
			if(row)row=row.nextSibling;
			var parent=this.xml_selectSingleNode(this.$P.dataXPath);
			var blank=this.makeBlankRow();
			parent.insertBefore(blank,row);
			return blank;
		},
		setDefaultValue:function(row){
			var cs=row.childNodes;
			for(var i=0,n=cs.length;i<n;++i) this.xml_setNodeValue(cs[i],'');
		},
		_insertPost:function(row){
			row=(row||this.$V.currentNode);
			if(this.isInsertedRow(row)){
				row.removeAttribute('xlv_ins');
				var old_tags=this.xml_selectNodes('child::*[@xlv_old]',row);
				for(var j=0;j<old_tags.length;++j) old_tags[j].removeAttribute('xlv_old');
			}
		},
		_insertCancel:function(row){
			row=(row||this.$V.currentNode);
			if(row&&this.isInsertedRow(row))row.parentNode.removeChild(row);
		},
		lookup:function(lookup_field,lookup_value){
			var node=this.getNode('', lookup_field+'="'+lookup_value+'"');
			return (node)? this.rowNumber(node):0;
		},
		map:function(lookup_field,lookup_value,map_field){
			var node=this.getNode('', lookup_field+'="'+lookup_value+'"');
			if(node){
				var map_node=this.xml_selectSingleNode(map_field,node);
				return (map_node)?this.xml_getNodeValue(map_node):null;
			}
			return null;
		},
		/**state**/
		isDirtyRow:function(row){
			row=(row||this.$V.currentNode);
			return row!=null &&(this.isUpdatedRow(row)||this.isInsertedRow(row)||this.isRemovedRow(row));
		},
		isDirty:function(){
			var n=this.xml_selectSingleNode(this.$P.dataXPath+'/*[@xlv_upd]|*[@xlv_ins]|*[@xlv_del]');
			return n?true:false;
		},
		isCompleted:function(){
			return true;
		},
		isEOB:function(row){//end of buffer
			row=(row||this.$V.currentNode);
			if(row) row=this.xml_selectSingleNode('following-sibling::'+row.tagName+'[1]',row);
			return row == null;
		},
		isEOF:function(row){
			return this.isCompleted()&&this.isEOB(row);
		},
		isBOF:function(row){
			row=(row||this.$V.currentNode);
			if(row)row=this.xml_selectSingleNode('preceding-sibling::'+row.tagName+'[1]',row);
			return row == null;
		},
		empty:function(){
			return this.$V.currentNode==null;
		},
		rows:function(){
			var ns=this.xml_selectNodes(this.$P.dataXPath+'/'+this.$P.rowName);
			return ns.length;
		},
		getRow:function(row_no){
			return this.xml_selectSingleNode(this.$P.dataXPath+'/'+this.$P.rowName+'['+row_no+']');
		},
		rowNumber:function(row){
			row=(row||this.$V.currentNode);
			if(row==null)return 0;
			var ns=this.xml_selectNodes('preceding-sibling::'+row.nodeName,row);
			return ns == null ? 1 : ns.length+1;
		},
		advanceRow:function(row){
			row=(row||this.$V.currentNode);
			var nrow;
			if(row){
				nrow=this.xml_selectSingleNode('following-sibling::'+this.$P.rowName+'[1]',row);
				if(!nrow) nrow=this.xml_selectSingleNode('preceding-sibling::'+this.$P.rowName+'[1]',row);
			}
			return nrow;
		},
		exception:function(){
		}
	}
};

